fix: release script squash-merge compat and backmerge automation#12162
fix: release script squash-merge compat and backmerge automation#121620xApotheosis merged 10 commits intodevelopfrom
Conversation
merged_untagged case was creating a private sync PR without checking if one already existed, unlike tagged_private_stale which had the guard. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
tagged_private_stale case was creating a private sync PR even when private was already content-identical to main (SHA mismatch due to propagation delay after a sync PR merges). Added a content diff check to bail early in that case. Same guard applied to the hotfix path. The merged_untagged case also gets the open-PR guard for belt-and-suspenders. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughRelease script now uses commit- and content-diff checks between branches to decide prerelease state, conditionally creates or reuses private sync PRs, creates and auto-merges backmerge PRs (main→develop) when needed, and updates logging across regular and hotfix flows. Changes
Sequence Diagram(s)sequenceDiagram
participant Release as Release Script
participant Git as Git (origin/main, origin/release, origin/private, develop)
participant GH as GitHub (PRs, Tags)
Release->>Git: fetch SHAs, commits and content diffs
alt release has no unique commits OR content identical to main
Release->>GH: log prerelease merged / stop
else
Release->>GH: create release tag
Release->>GH: query open private sync PRs
alt open private PR exists
GH-->>Release: return PR info
Release->>GH: log reuse existing private PR
else
Release->>Git: compute private vs main content diff
alt private differs from main
Release->>GH: create private sync PR
GH-->>Release: return PR URL
else
Release->>GH: log "private in sync, skip PR"
end
end
Release->>Git: check commits main→develop
alt backmerge needed
Release->>GH: create or find backmerge PR (main→develop)
Release->>GH: enable auto-merge (merge-commit)
GH-->>Release: return PR + auto-merge status
else
Release->>GH: log "no backmerge needed"
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
scripts/release.ts (1)
805-809: Content guard applied consistently to Hotfix path — butmerged_untaggedguard is missing.The content diff check mirrors the Regular release path correctly. However, the Hotfix
merged_untaggedcase (lines 781-798) still creates PRs unconditionally without checking for existing ones, unlike the guarded Regular release path at lines 613-630.Per the PR description intent to "add an open-PR guard to
merged_untagged", consider applying the same pattern to the Hotfix path for full consistency:Proposed fix for Hotfix merged_untagged guard
case 'merged_untagged': { console.log(chalk.green(`Hotfix merged to main. Tagging ${nextVersion}...`)) await git().checkout(['main']) await git().pull() await git().tag(['-a', nextVersion, '-m', nextVersion]) console.log(chalk.green('Pushing tag...')) await git().push(['origin', '--tags']) console.log(chalk.green(`Tagged ${nextVersion}.`)) - console.log(chalk.green('Creating PR to sync private to main...')) - const privatePrUrl = await createPr({ - base: 'private', - head: 'main', - title: `chore: sync private to ${nextVersion}`, - body: `Sync private branch to main after hotfix ${nextVersion}.`, - }) - console.log(chalk.green(`Private sync PR created: ${privatePrUrl}`)) + const existingPrivatePrAfterTag = await findOpenPr('main', 'private') + if (existingPrivatePrAfterTag) { + console.log( + chalk.yellow( + `Private sync PR already open: #${existingPrivatePrAfterTag.number}. Merge it on GitHub.`, + ), + ) + } else { + console.log(chalk.green('Creating PR to sync private to main...')) + const privatePrUrl = await createPr({ + base: 'private', + head: 'main', + title: `chore: sync private to ${nextVersion}`, + body: `Sync private branch to main after hotfix ${nextVersion}.`, + }) + console.log(chalk.green(`Private sync PR created: ${privatePrUrl}`)) + } - console.log(chalk.green('Creating backmerge PR (main -> develop)...')) - const backmergeUrl = await createPr({ - base: 'develop', - head: 'main', - title: `chore: backmerge ${nextVersion} into develop`, - body: `Backmerge main into develop after hotfix ${nextVersion} to sync cherry-picked commits.`, - }) - console.log(chalk.green(`Backmerge PR created: ${backmergeUrl}`)) - console.log(chalk.green('Merge both PRs on GitHub to complete the hotfix.')) + const existingBackmergePrAfterTag = await findOpenPr('main', 'develop') + if (existingBackmergePrAfterTag) { + console.log( + chalk.yellow( + `Backmerge PR already open: #${existingBackmergePrAfterTag.number}. Merge it on GitHub.`, + ), + ) + } else { + console.log(chalk.green('Creating backmerge PR (main -> develop)...')) + const backmergeUrl = await createPr({ + base: 'develop', + head: 'main', + title: `chore: backmerge ${nextVersion} into develop`, + body: `Backmerge main into develop after hotfix ${nextVersion} to sync cherry-picked commits.`, + }) + console.log(chalk.green(`Backmerge PR created: ${backmergeUrl}`)) + } + console.log(chalk.green('Merge PRs on GitHub to complete the hotfix.')) break }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/release.ts` around lines 805 - 809, The Hotfix `merged_untagged` path is missing the open-PR guard present in the Regular release flow; modify the Hotfix branch handling that uses `privateDiffHotfix`/`merged_untagged` so it first queries for an existing open PR (reuse the same check used in the Regular release path) and only calls the PR creation routine (e.g., the existing `createPullRequest`/createPR call) when no open PR is found; if an open PR exists, skip creating a new one and log or exit the same way the Regular path does.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@scripts/release.ts`:
- Around line 805-809: The Hotfix `merged_untagged` path is missing the open-PR
guard present in the Regular release flow; modify the Hotfix branch handling
that uses `privateDiffHotfix`/`merged_untagged` so it first queries for an
existing open PR (reuse the same check used in the Regular release path) and
only calls the PR creation routine (e.g., the existing
`createPullRequest`/createPR call) when no open PR is found; if an open PR
exists, skip creating a new one and log or exit the same way the Regular path
does.
…ate_private_sync_pr_v2
…rge auto-merge - idle case: use git diff content check instead of SHA equality for prereleaseMerged - squash merges diverge SHAs even when content matches - tagged_private_stale (regular + hotfix): set auto-merge with merge commit strategy on backmerge PR so it lands without manual intervention Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/release.ts`:
- Around line 639-645: The early break when privateDiff is empty stops further
backmerge logic; remove the break (or replace it with no-op) so after logging
"Private is already in sync with main content-wise. Nothing to do." the script
continues to the subsequent backmerge checks and PR creation. Update both
occurrences around the privateDiff check (the const privateDiff = await
git().diff([...]) blocks and the similar block at the later lines) to log but
not exit the loop so the backmerge automation (main -> develop PR creation) can
still run.
- Around line 518-520: The prereleaseMerged boolean currently only checks
SHA/content mismatch (releaseMatchesMain) and can be true when origin/release is
behind origin/main; update the logic so prereleaseMerged is only true when
origin/release actually contains commits not present on origin/main (i.e.,
release is ahead), not merely when SHAs/content differ. Replace the current
check that sets prereleaseMerged (which references releaseSha, mainSha,
releaseMatchesMain and git().diff) with a check that runs a git rev-list/log
between origin/release and origin/main (e.g., list commits in origin/release
^origin/main) and considers prereleaseMerged true only if that list is
non-empty.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
…eak in tagged_private_stale - idle: replace SHA/content-diff prereleaseMerged with commit-ahead check (origin/main..origin/release) - prevents false positive when release is *behind* main (e.g. post-hotfix), which would have routed into release PR path with 0 commits - tagged_private_stale (regular + hotfix): remove early break when private is content-synced - script must still evaluate backmerge PR creation even when private sync is a no-op Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/release.ts`:
- Around line 669-687: The backmerge branch creates auto-merge only when a new
PR is created, so reruns where findOpenPr('main', 'develop') returns an
existingBackmerge never enable auto-merge; update the logic so after detecting
an existingBackmerge you extract its identifier/URL (from the existingBackmerge
object returned by findOpenPr) and run the same pify(exec)(`gh pr merge --auto
--merge ${prIdentifierOrUrl}`) call to enable auto-merge, and apply the
identical change to the hotfix backmerge block (the other block that uses
createPr/getCommitMessages and gh pr merge).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
There was a problem hiding this comment.
♻️ Duplicate comments (1)
scripts/release.ts (1)
671-689:⚠️ Potential issue | 🟠 MajorAuto-merge is still not enabled when a backmerge PR already exists.
When
existingBackmergeis found, the script currently does nothing. On reruns, this can leave backmerge PRs open without auto-merge configured and stall automation.🔧 Minimal fix (apply in both regular + hotfix backmerge blocks)
const existingBackmerge = await findOpenPr('main', 'develop') -if (!existingBackmerge) { +if (existingBackmerge) { + console.log(chalk.yellow(`Backmerge PR already open: #${existingBackmerge.number}.`)) + console.log(chalk.green('Setting auto-merge with merge commit strategy...')) + await pify(exec)(`gh pr merge --auto --merge ${existingBackmerge.number}`) + console.log(chalk.green('Auto-merge set. Backmerge will merge automatically when CI passes.')) +} else { const mainDevelopCommits = await getCommitMessages('origin/develop..origin/main') if (mainDevelopCommits.length > 0) { console.log(chalk.green('Creating backmerge PR (main -> develop)...')) const backmergeUrl = await createPr({ base: 'develop', head: 'main', title: `chore: backmerge ${nextVersion} into develop`, body: `Backmerge main into develop after release ${nextVersion}.`, }) console.log(chalk.green(`Backmerge PR created: ${backmergeUrl}`)) console.log(chalk.green('Setting auto-merge with merge commit strategy...')) await pify(exec)(`gh pr merge --auto --merge ${backmergeUrl}`) console.log(chalk.green('Auto-merge set. Backmerge will merge automatically when CI passes.')) } }Also applies to: 864-882
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/release.ts` around lines 671 - 689, The code currently skips configuring auto-merge when findOpenPr returns an existingBackmerge, leaving PRs open without auto-merge; update the logic in the backmerge block around existingBackmerge (and mirror the same change in the hotfix backmerge block) so that if existingBackmerge is truthy you call the same auto-merge command used after createPr (i.e., invoke pify(exec) with the gh pr merge --auto --merge argument using existingBackmerge's URL or number), logging the same progress messages as when a PR is newly created so reruns will enable auto-merge on already-open backmerge PRs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@scripts/release.ts`:
- Around line 671-689: The code currently skips configuring auto-merge when
findOpenPr returns an existingBackmerge, leaving PRs open without auto-merge;
update the logic in the backmerge block around existingBackmerge (and mirror the
same change in the hotfix backmerge block) so that if existingBackmerge is
truthy you call the same auto-merge command used after createPr (i.e., invoke
pify(exec) with the gh pr merge --auto --merge argument using
existingBackmerge's URL or number), logging the same progress messages as when a
PR is newly created so reruns will enable auto-merge on already-open backmerge
PRs.
…ate_private_sync_pr_v2
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
scripts/release.ts (1)
828-836: Consider enabling auto-merge immediately in hotfixmerged_untagged.The backmerge PR created here doesn't have auto-merge enabled, unlike when it's created/found in
tagged_private_stale. If the user doesn't re-run the script after this state, the backmerge will sit waiting for manual merge.For consistency with the stated automation goals, consider enabling auto-merge immediately:
♻️ Optional: Enable auto-merge on backmerge creation
console.log(chalk.green(`Backmerge PR created: ${backmergeUrl}`)) - console.log(chalk.green('Merge both PRs on GitHub to complete the hotfix.')) + console.log(chalk.green('Setting auto-merge with merge commit strategy...')) + await pify(exec)(`gh pr merge --auto --merge ${backmergeUrl}`) + console.log( + chalk.green('Auto-merge set. Backmerge will merge automatically when CI passes.'), + ) + console.log(chalk.green('Merge the private sync PR on GitHub to complete the hotfix.')) break🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/release.ts` around lines 828 - 836, The backmerge PR created by the createPr call (creating backmergeUrl) in the hotfix merged_untagged path does not enable auto-merge, so the PR can stall; update the implementation around createPr (or immediately after obtaining backmergeUrl) to enable auto-merge for that PR—either by extending createPr to accept and apply auto-merge/merge_method options or by invoking the same auto-merge helper used elsewhere (the logic used for tagged_private_stale) immediately after the PR is created; ensure you reference createPr and backmergeUrl when adding the auto-merge step so the backmerge PR is configured the same way as the other flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@scripts/release.ts`:
- Around line 828-836: The backmerge PR created by the createPr call (creating
backmergeUrl) in the hotfix merged_untagged path does not enable auto-merge, so
the PR can stall; update the implementation around createPr (or immediately
after obtaining backmergeUrl) to enable auto-merge for that PR—either by
extending createPr to accept and apply auto-merge/merge_method options or by
invoking the same auto-merge helper used elsewhere (the logic used for
tagged_private_stale) immediately after the PR is created; ensure you reference
createPr and backmergeUrl when adding the auto-merge step so the backmerge PR is
configured the same way as the other flow.
0xApotheosis
left a comment
There was a problem hiding this comment.
Logic sane, though can't test with the current release PR open. Happy for you to confirm it works by shipping the release!
Description
Three fixes to the release state machine in `scripts/release.ts`, all rooted in the same underlying problem: the script was designed (in #12110) assuming rebase merging (same SHAs post-merge), but the repo uses squash merging.
1. `idle` case - content diff instead of SHA equality for `prereleaseMerged`
Before:
```ts
const prereleaseMerged = releaseSha !== mainSha
```
After squash-merging a release PR, `releaseSha !== mainSha` is always true (squash creates a new commit), so the script always thought there was a pending release to cut even right after a fresh one. Fixed by comparing actual file content:
```ts
const releaseMatchesMain = !(await git().diff(['origin/main', 'origin/release']))
const prereleaseMerged = releaseSha !== mainSha && !releaseMatchesMain
```
2. `tagged_private_stale` (regular release) - add backmerge PR with auto-merge
After main gets tagged and private is synced, main has diverged from develop (the squash-merged release commit isn't in develop's history). The script was leaving this cleanup to be done manually.
Now creates a `main -> develop` backmerge PR automatically and sets auto-merge with merge commit strategy:
```ts
await pify(exec)(`gh pr merge --auto --merge ${backmergeUrl}`)
```
Merge commit strategy is intentional - preserves main's commit in develop's history so the merge base is correct for future releases.
3. `tagged_private_stale` (hotfix) - same auto-merge treatment
The hotfix path already created the backmerge PR but wasn't setting auto-merge. Now consistent with the regular release path.
Issue (if applicable)
closes #
Risk
Low - release script only, no production code touched. Worst case is a script error requiring manual intervention (same as before).
Testing
Engineering
Run `pnpm release` after a release has been tagged and private is stale:
For the `idle` fix: run `pnpm release` right after squash-merging a release - should land in the "fresh start" sub-state (create prerelease PR) instead of "prerelease merged".
Operations
Summary by CodeRabbit